package streamdef;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

public class StreamCondition {
	// streamID of resulting events/tuples
	private long streamID;

	// internal streamID -> external streamID, necessary for when occurrence and
	// non-occurrence of the same stream is defined
	// -> retrieved from the FROM-statement
	private HashMap<Long, Long> internalStreamIDToExternalStreamID;

	// fieldNames of resulting events/tuples -> originating internal streamID
	private HashMap<String, Long> fieldNameToInternalOrigStreamID;
	// fieldNames of resulting events/tuples -> originating external streamID
	private HashMap<String, Long> fieldNameToExternalOrigStreamID;
	// fieldNames of resulting events/tuples -> originating fieldName
	private HashMap<String, String> fieldNameToOrigFieldName;

	// RANGE-statements, undefined -> -1
	private HashMap<Long, Double> internalStreamIDToRangeValue;
	private Vector<Long> internalStreamIDsForNonOccurrence;

	// AND-statements -> more than one subQuery possible
	private Vector<SubCondition> subConditions;

	// valid event sequences according to the BEFORE-statements
	// of the occurring events
	// -> if ONE of the Vectors is occurring -> match !
	private Vector<Vector<Long>> possibleEventSequences;

	private Vector<Long> internalStreamIDsNotInBEFOREStatementDefined;
	private Vector<Long> internalStreamIDsInBEFOREStatementDefined;

	private Vector<Long> internalStreamIDsNotInStatementAndNonOccurring;

	public StreamCondition() {
		this(-1);
	}

	public StreamCondition(long streamID) {
		this.streamID = streamID;

		internalStreamIDToExternalStreamID = new HashMap<Long, Long>();
		fieldNameToInternalOrigStreamID = new HashMap<String, Long>();
		fieldNameToExternalOrigStreamID = new HashMap<String, Long>();
		fieldNameToOrigFieldName = new HashMap<String, String>();
		internalStreamIDToRangeValue = new HashMap<Long, Double>();
		internalStreamIDsForNonOccurrence = new Vector<Long>();
		subConditions = new Vector<SubCondition>();
		possibleEventSequences = new Vector<Vector<Long>>();
		internalStreamIDsNotInBEFOREStatementDefined = new Vector<Long>();
		internalStreamIDsInBEFOREStatementDefined = new Vector<Long>();
		internalStreamIDsNotInStatementAndNonOccurring = new Vector<Long>();
	}

	protected void finalize() {
		internalStreamIDToExternalStreamID.clear();
		fieldNameToInternalOrigStreamID.clear();
		fieldNameToExternalOrigStreamID.clear();
		fieldNameToOrigFieldName.clear();
		internalStreamIDToRangeValue.clear();
		internalStreamIDsForNonOccurrence.clear();
		subConditions.clear();
		possibleEventSequences.clear();
		internalStreamIDsNotInBEFOREStatementDefined.clear();
		internalStreamIDsInBEFOREStatementDefined.clear();
		internalStreamIDsNotInStatementAndNonOccurring.clear();
	}

	public long getStreamID() {
		return streamID;
	}

	public void setStreamID(long streamID) {
		this.streamID = streamID;
	}

	public Long getExternalStreamID(long internalStreamID) {
		Long id = internalStreamIDToExternalStreamID.get(internalStreamID);
		return id;
	}

	public Long getOrigExternalStreamID(String fieldName) {
		return fieldNameToExternalOrigStreamID.get(fieldName);
	}

	public Long getOrigInternalStreamID(String fieldName) {
		return fieldNameToInternalOrigStreamID.get(fieldName);
	}

	public String getOrigFieldName(String fieldName) {
		return fieldNameToOrigFieldName.get(fieldName);
	}

	// the stream ID's mentioned in the FROM-statement
	public Vector<Long> getDefinedInternalStreamIDs() {
		Vector<Long> result = new Vector<Long>();
		Iterator<Long> iterator = internalStreamIDToRangeValue.keySet()
				.iterator();
		while (iterator.hasNext()) {
			Long key = (Long) iterator.next();
			result.add(key);
		}

		return result;
	}

	public Vector<Long> getUsedExternalStreamIDs() {
		Vector<Long> result = new Vector<Long>();
		Iterator<Long> iterator = internalStreamIDToExternalStreamID.keySet()
				.iterator();
		while (iterator.hasNext()) {
			Long key = (Long) iterator.next();
			Long value = internalStreamIDToExternalStreamID.get(key);
			if (!result.contains(value))
				result.add(value);
		}

		return result;
	}

	public boolean externalStreamIDIsUsed(long externalStreamID) {
		Vector<Long> internalStreamIDs = getDefinedInternalStreamIDs();
		for (int i = 0; i < internalStreamIDs.size(); ++i) {
			if (getExternalStreamID(internalStreamIDs.get(i)) == externalStreamID)
				return true;
		}

		return false;
	}

	public Vector<Long> getInternalStreamIDsForExternalStreamID(long externalID) {
		Vector<Long> result = new Vector<Long>();
		Iterator<Long> iterator = internalStreamIDToExternalStreamID.keySet()
				.iterator();
		while (iterator.hasNext()) {
			Long key = (Long) iterator.next();
			if (getExternalStreamID(key) == externalID)
				result.add(key);
		}

		return result;
	}

	public Double getRangeForInternalStreamID(long internalID) {
		return internalStreamIDToRangeValue.get(internalID);
	}

	public boolean mayOccur(long intStreamID) {
		return internalStreamIDsForNonOccurrence
				.contains(new Long(intStreamID)) == false;
	}

	public Vector<Long> getNonOccuringInternalStreamIDs() {
		return this.internalStreamIDsForNonOccurrence;
	}

	public boolean definedInABEFOREStatement(long internalID) {
		return internalStreamIDsInBEFOREStatementDefined.contains(internalID) == true;
	}

	public Vector<Long> getInternalStreamIDsNotInBEFOREStatementDefined() {
		return internalStreamIDsNotInBEFOREStatementDefined;
	}

	public Vector<Long> getInternalStreamIDsInBEFOREStatementDefined() {
		return internalStreamIDsInBEFOREStatementDefined;
	}

	// event format defined in the first line of the query
	public void addField(String newFieldName, Long origInternalStreamID,
			String origFieldName) {
		this.fieldNameToInternalOrigStreamID.put(newFieldName,
				origInternalStreamID);
		this.fieldNameToExternalOrigStreamID.put(newFieldName,
				getExternalStreamID(origInternalStreamID));
		this.fieldNameToOrigFieldName.put(newFieldName, origFieldName);
	}

	public void addInternalStreamID(long internalID, long externalID,
			double range, boolean mayOccur) {
		this.internalStreamIDToExternalStreamID.put(new Long(internalID),
				new Long(externalID));
		this.internalStreamIDToRangeValue.put(new Long(internalID), new Double(
				range));

		if (!mayOccur)
			this.internalStreamIDsForNonOccurrence.add(new Long(internalID));
	}

	public void addInternalStreamID(long internalID, long externalID,
			double range) {
		addInternalStreamID(internalID, externalID, range, true);
	}

	public void addInternalStreamID(long internalID, long externalID) {
		addInternalStreamID(internalID, externalID, -1, true);
	}

	public int numberOfSubQueries() {
		return subConditions.size();
	}

	public Vector<SubCondition> getSubQueries() {
		return subConditions;
	}

	public void addSubQuery(SubCondition subCondition) {
		this.subConditions.add(subCondition);
	}

	public void calculateEventSequences() {
		Vector<Long> allowedEventsNotYetInSequence = this
				.getDefinedInternalStreamIDs();
		for (int i = 0; i < internalStreamIDsForNonOccurrence.size(); ++i)
			allowedEventsNotYetInSequence
					.removeElement(internalStreamIDsForNonOccurrence.get(i));

		for (int i = 0; i < subConditions.size(); ++i) {
			Vector<ConditionStatement> statements = subConditions.get(i)
					.getQueryStatements();
			for (int j = 0; j < statements.size(); ++j) {
				ConditionStatement stat = statements.get(j);
				if (stat.type == streamdef.DEFINES.ConditionStatementType.TIMERELATION_TWOSTREAMS) {
					long id1 = ((ConditionStatementTimeRelationTwoStreams) stat).internalStreamID1;
					long id2 = ((ConditionStatementTimeRelationTwoStreams) stat).internalStreamID2;

					if (mayOccur(id1) && mayOccur(id2)) {
						calculatePossibleEventSequencesWithNewRelation(id1, id2);
						allowedEventsNotYetInSequence.remove(new Long(id1));
						allowedEventsNotYetInSequence.remove(new Long(id2));
					}

					if (!internalStreamIDsInBEFOREStatementDefined
							.contains(id1))
						internalStreamIDsInBEFOREStatementDefined.add(id1);
					if (!internalStreamIDsInBEFOREStatementDefined
							.contains(id2))
						internalStreamIDsInBEFOREStatementDefined.add(id2);
				}
			}
		}

		calculateInternalStreamIDsNotInBeforeStatementDefined();
		mergeInternalStreamIDsNotInBeforeStatementDefinedWithEventSequences(allowedEventsNotYetInSequence);
	}

	@SuppressWarnings("unchecked")
	private void calculatePossibleEventSequencesWithNewRelation(long id1,
			long id2) {
		// no sequences -> generate the one provided
		if (possibleEventSequences.size() == 0) {
			Vector<Long> newV = new Vector<Long>();
			newV.add(id1);
			newV.add(id2);
			possibleEventSequences.add(newV);
		} else {
			Vector<Vector<Long>> prevSequences = possibleEventSequences;
			possibleEventSequences = new Vector<Vector<Long>>();

			for (int i = 0; i < prevSequences.size(); ++i) {
				Vector<Long> prevSeq = prevSequences.get(i);

				if (!prevSeq.contains(id2) && !prevSeq.contains(id1)) {
					for (int j = 0; j <= prevSeq.size(); ++j) {
						Vector<Long> newSeq = (Vector<Long>) (prevSeq.clone());
						newSeq.add(j, id1);
						possibleEventSequences.add(newSeq);
					}
				} else if (prevSeq.contains(id2) && !prevSeq.contains(id1)) {
					for (int j = 0; j <= prevSeq.indexOf(id2); ++j) {
						Vector<Long> newSeq = (Vector<Long>) (prevSeq.clone());
						newSeq.add(j, id1);
						possibleEventSequences.add(newSeq);
					}
				} else if (prevSeq.contains(id1) && !prevSeq.contains(id2)) {
					for (int j = prevSeq.indexOf(id1) + 1; j <= prevSeq.size(); ++j) {
						Vector<Long> newSeq = (Vector<Long>) (prevSeq.clone());
						newSeq.add(j, id2);
						possibleEventSequences.add(newSeq);
					}
				} else if (prevSeq.contains(id1) && prevSeq.contains(id2)) {
					if (prevSeq.indexOf(id1) < prevSeq.indexOf(id2)) {
						Vector<Long> newSeq = (Vector<Long>) (prevSeq.clone());
						possibleEventSequences.add(newSeq);
					}
				}
			}

			prevSequences = possibleEventSequences;
			possibleEventSequences = new Vector<Vector<Long>>();

			for (int i = 0; i < prevSequences.size(); ++i) {
				Vector<Long> prevSeq = prevSequences.get(i);

				if (prevSeq.contains(id1) && !prevSeq.contains(id2)) {
					for (int j = prevSeq.indexOf(id1) + 1; j <= prevSeq.size(); ++j) {
						Vector<Long> newSeq = (Vector<Long>) (prevSeq.clone());
						newSeq.add(j, id2);
						possibleEventSequences.add(newSeq);
					}
				}
			}
		}
	}

	private void calculateInternalStreamIDsNotInBeforeStatementDefined() {
		Vector<Long> allIDs = this.getDefinedInternalStreamIDs();
		for (int i = 0; i < allIDs.size(); ++i)
			if (!internalStreamIDsInBEFOREStatementDefined.contains(new Long(
					allIDs.get(i))))
				internalStreamIDsNotInBEFOREStatementDefined.add(new Long(
						allIDs.get(i)));
	}

	// pre: possibleEventSequences must be constructed
	private void mergeInternalStreamIDsNotInBeforeStatementDefinedWithEventSequences(
			Vector<Long> allowedEventsNotYetInSequence) {
		Vector<Vector<Long>> prev = possibleEventSequences;

		for (int i = 0; i < allowedEventsNotYetInSequence.size(); ++i) {
			long intID = allowedEventsNotYetInSequence.get(i);

			// only valid IDs in sequence of course
			if (!internalStreamIDsForNonOccurrence.contains(intID)) {
				if (possibleEventSequences.size() == 0) {
					Vector<Long> firstSeq = new Vector<Long>();
					firstSeq.add(new Long(intID));
					possibleEventSequences.add(firstSeq);
				} else {
					// the new Vector of sequences, including the current ID
					Vector<Vector<Long>> curr = new Vector<Vector<Long>>();

					// for each old sequence without the current ID
					for (int j = 0; j < prev.size(); ++j) {
						Vector<Long> prevSeq = prev.get(j);

						// for each index in the old sequence
						for (int k = 0; k <= prevSeq.size(); ++k) {
							// make a copy of the old sequence
							Vector<Long> newSeq = new Vector<Long>();
							for (int l = 0; l < prevSeq.size(); ++l)
								newSeq.add(new Long(prevSeq.get(l)));
							// place the current ID in the index
							newSeq.add(k, new Long(intID));
							// add the new sequence to the new Vector
							curr.add(newSeq);
						}
					}

					prev = curr;
				}
			}
		}

		possibleEventSequences = prev;
	}

	public boolean mayNotOccurNotInAnyStatement(long intID) {
		return internalStreamIDsNotInStatementAndNonOccurring.contains(intID);
	}

	public Vector<Long> getInternalStreamIDsNotInStatementAndNonOccurring() {
		return internalStreamIDsNotInStatementAndNonOccurring;
	}

	public void calculateForbiddenEventsNotInStatements() {
		for (int i = 0; i < internalStreamIDsForNonOccurrence.size(); ++i) {
			long intID = internalStreamIDsForNonOccurrence.get(i);
			boolean found = false;
			for (int j = 0; j < subConditions.size() && !found; ++j) {
				SubCondition sq = subConditions.get(j);
				if (sq.getUsedInternalStreamIDs().contains(intID))
					found = true;
			}

			if (!found)
				internalStreamIDsNotInStatementAndNonOccurring.add(intID);
		}
	}

	// pairs of internalIDs
	public Vector<Vector<Long>> getPossibleEventSequences() {
		return possibleEventSequences;
	}
}
